﻿using DaVikingCode.RectanglePacking;
using System;
using System.Collections.Generic;
using UnityEngine;

namespace Core
{
    public class AssetPackerSystem : EntitySystem, IAwake, IEndUpdate
	{
		private float pixelsPerUnit = 100.0f;
		private int textureSize = 2048;

		private List<AtlasData> atlasDatas = new List<AtlasData>();
		private Dictionary<string, TextureData> textureDatas = new Dictionary<string, TextureData>();

		private bool isNeedUpdateAtlas;

        public void Awake()
		{
            Subscribe<PackerImageEx>(PackerImageEx);
			Subscribe<ProcessAssetPacker>(ProcessAssetPacker);
			Subscribe<GetSpriteFromAssetPacker>(GetSpriteFromAssetPacker);
			Subscribe<OnImageExDestroy>(OnImageExDestroy);
			Subscribe<ChangeImageExTexture>(ChangeImageExTexture);
        }

		private void ChangeImageExTexture(ref ChangeImageExTexture data)
		{
            var imageEx = data.imageEx;
			RemoveImageEx(imageEx);
            imageEx.imageTexturePath = data.texturePath;
			if (TryPackerImageEx(imageEx, out var sprite))
			{
                ChangeImageSprite(imageEx, sprite);
            }
			else
			{
                isNeedUpdateAtlas = true;
            }
        }

        private void OnImageExDestroy(ref OnImageExDestroy data)
		{
			var imageEx = data.imageEx;
			RemoveImageEx(imageEx);
        }

		private void RemoveImageEx(ImageEx imageEx)
		{
            if (!textureDatas.TryGetValue(imageEx.imageTexturePath, out var textureData))
                return;

            textureData.imageExs.Remove(imageEx);
            if (textureData.imageExs.Count == 0)
            {
                GameObject.DestroyImmediate(textureData.sprite);
                textureDatas.Remove(imageEx.imageTexturePath);
            }
        }

        private void GetSpriteFromAssetPacker(ref GetSpriteFromAssetPacker data)
		{
			var sprite = GetSprite(data.texturePath);
			data.sprite = sprite;
		}

        private void PackerImageEx(ref PackerImageEx data)
		{
			TryPackerImageEx(data.imageEx, out var _);
        }

		private bool TryPackerImageEx(ImageEx imageEx, out Sprite sprite)
		{
			sprite = null;

            if (imageEx == null || string.IsNullOrEmpty(imageEx.imageTexturePath))
                return false;
            if (imageEx.img == null || imageEx.img.sprite == null || imageEx.img.sprite.texture == null)
                return false;
            if (imageEx.img.sprite.texture.width > textureSize || imageEx.img.sprite.texture.height > textureSize)
                return false;

            if (!textureDatas.TryGetValue(imageEx.imageTexturePath, out var textureData))
            {
                textureData = new TextureData(imageEx.img.sprite.texture);
                textureDatas[imageEx.imageTexturePath] = textureData;
                textureData.imageExs.Add(imageEx);
				return false;
            }
			else
			{
				sprite = textureData.sprite;
                textureData.imageExs.Add(imageEx);
				return true;
            }
        }

        private void ProcessAssetPacker(ref ProcessAssetPacker data) 
		{
			UpdateAtlasAndApplySprite();
        }

		private void UpdateAtlasAndApplySprite()
        {
            isNeedUpdateAtlas = false;

            CreatePack();
            ApplySprite();
        }

		protected void CreatePack() 
		{
            HashSet<string> texturePaths = new HashSet<string>(textureDatas.Keys);

            const int padding = 1;

			var atlasIndex = 0;

            while (texturePaths.Count > 0) 
			{
                RectanglePacker packer = null;
				Texture2D texture = null;

                if (atlasIndex >= atlasDatas.Count - 1)
				{
					// 超过就创建一个新图集
                    packer = new RectanglePacker(textureSize, textureSize, padding);
                    texture = new Texture2D(textureSize, textureSize, TextureFormat.RGBA32, false);
                    texture.name = $"AssetPackerAtlas{atlasIndex}";

                    var atlasData = new AtlasData() { texture = texture , packer = packer };
                    atlasDatas.Add(atlasData);
                }

                packer = atlasDatas[atlasIndex].packer;
				packer.reset(textureSize, textureSize, padding);
                texture = atlasDatas[atlasIndex].texture;

                foreach (var texturePath in texturePaths)
					packer.insertRectangle(textureDatas[texturePath].texture.width, textureDatas[texturePath].texture.height, texturePath);

                packer.packRectangles();

                if (packer.rectangleCount > 0) 
				{
					for (int j = 0; j < packer.rectangleCount; j++) 
					{
						var rect = packer.GetRectangle(j);

						string successPackPath = rect.id;
						if (!textureDatas.TryGetValue(successPackPath, out var subTextureData))
							throw new Exception("找不到构建图集的元素");

						CopyTexture(subTextureData.texture, texture, rect);

                        var sprite = Sprite.Create(texture,
							new Rect(rect.x, rect.y, rect.width, rect.height),
							Vector2.zero, pixelsPerUnit, 0, SpriteMeshType.FullRect);
						subTextureData.sprite = sprite;

						texturePaths.Remove(successPackPath);
                    }

					if (!IsSupportGraphicsCopy())
						texture.Apply();
                }

                atlasIndex++;
            }
		}

		private void CopyTexture(Texture2D src, Texture2D dest, IntegerRectangle destRect)
		{
            if (IsSupportGraphicsCopy())
            {
                Graphics.CopyTexture(src, 0, 0, 0, 0, src.width, src.height, dest, 0, 0, destRect.x, destRect.y);
            }
			else
			{
				var srcRawData = src.GetRawTextureData<Color32>();
				var destRawData = dest.GetRawTextureData<Color32>();
				var startPos = destRect.x + destRect.y * dest.width;

                for (int i = 0; i < src.height; i++)
				{
                    Unity.Collections.NativeArray<Color32>.Copy(srcRawData, i * src.width, destRawData, startPos + i * dest.width, src.width);
                }
			}
		}

		private bool IsSupportGraphicsCopy()
		{
			return (SystemInfo.copyTextureSupport & UnityEngine.Rendering.CopyTextureSupport.Basic) > 0;
        }

		private void ApplySprite()
		{
			foreach (var textureDataPair in textureDatas)
			{
				var textureData = textureDataPair.Value;

				foreach (var imageEx in textureData.imageExs)
				{
					ChangeImageSprite(imageEx, textureData.sprite);
                }
            }
		}

		private void ChangeImageSprite(ImageEx imageEx, Sprite sprite)
		{
#if UNITY_EDITOR
            // 编辑器模式下为了方便查看原图片位置，修改为overrideSprite
            imageEx.img.overrideSprite = sprite;
#else
			imageEx.img.sprite = sprite;
#endif
        }

        public void Destroy() 
		{
            textureDatas.Clear();
        }

		public Sprite GetSprite(string texturePath) 
		{
			if (textureDatas.TryGetValue(texturePath, out var data))
				return data.sprite;

			return null;
		}

        public void EndUpdate()
        {
			if (isNeedUpdateAtlas)
				UpdateAtlasAndApplySprite();
        }

        public class AtlasData
		{
			public Texture2D texture;
			public RectanglePacker packer;
        }

        public class TextureData
        {
            public Texture2D texture;
            public Sprite sprite;
            public HashSet<ImageEx> imageExs = new HashSet<ImageEx>();

            public TextureData(Texture2D texture)
            {
                this.texture = texture;
            }
        }
    }

    internal struct PackerImageEx : IEventData
	{
		public ImageEx imageEx;

		public PackerImageEx(ImageEx imageEx)
		{
			this.imageEx = imageEx;
		}
    }

	internal struct ProcessAssetPacker : IEventData
	{

	}

	internal struct GetSpriteFromAssetPacker : IEventData
	{
		public string texturePath;
		public GetSpriteFromAssetPacker(string texturePath)
		{
			this.texturePath = texturePath;
			this.sprite = null;
		}

        public Sprite sprite;
	}
}
